Akai的数学作业

Time Limit: 10 Sec Memory Limit: 128 MB

Description

这里是广袤无垠的宇宙这里是一泻千里的银河,这里是独一无二的太阳系,这里是蔚蓝色的地球
  这里,就是这里,是富饶的中国大陆!
  这里是神奇的河北大地,这里是美丽的唐山,这里是神话般的唐山一中,这里是Akai曾经的教室
  黑板上还留有当年Akai做过的数学作业,其实也并不是什么很困难的题目:
  “
    给出一个一元n次方程:
    a0 + a1x + a 2 x2 +…+ anxn= 0
    求此方程的所有有理数解。
  ”
  Akai至今还深刻记得当年熬夜奋战求解的时光,他甚至还能记得浪费了多少草稿纸。
  但是却怎么也想不起来最后的答案是多少了,你能帮助他么?

Input

第一行一个整数n。第二行n+1个整数,分别代表a0 到 an

Output

第一行输出一个整数t,表示有理数解的个数

接下来t行,每行表示一个解

解以分数的形式输出,要求分子和分母互质,且分母必须是正整数特殊的,如果这个解是一个整数,那么直接把这个数输出

等价的解只需要输出一次

所有解按照从小到大的顺序输出

Sample Input

3
 -24 14 29 6

Sample Output

3
 -4
 -3/2
 2/3

HINT

对于30%的数据,n <= 10
  对于100%的数据,n <= 100,|a i| <= 2*10^7,an ≠ 0

Main idea

给出一个一元n次方程:A0+A1x+A2x^2+…+An*x^n,求出这个方程的所有有理数解。

Solution

这必然是一道数论题。首先我们发现了题目的一个非常重要的特征:求的是有理数解
  立马想到了分解因式,因为要的是有理数解,所以原方程肯定可以表示成:

img

x就是q/p。

然后再来思考一下。我们先从最简单的情况开始处理,也就是A0≠0,An≠0的情况。
  显然可以知道p一定是An分出来的,q一定是A0分出来的,那么一定有p是An的约数,q是A0的约数,那么这时候所有的情况就应该是

img

仔细推一下式子,发现了一个规律:几个约数相乘的情况所表达出的集合和不考虑相乘情况的集合是一样的!那么处理就简单了很多。

由于可能有前几项系数=0的情况,所以我们从A0的想法出发,找到第一个系数非0的项将这一项的约数存下来(如果不是A0的话则在答案中加一个0),然后从后往前找找到第一个非0的存下它的约数。然后O(约数个数)^2枚举任意两种情况的q/p放到原式里面判断(答案有可能是负数所以还要检查一下-q/p可不可行)然后在检查的时候发现了一个问题,数字要么精度误差过大要么就是爆出int范围了,我们想到了通分,分子分母同乘上p^n,避免了精度问题。

以n=3的举个例子:将原式

img

转化为

img

然后我们就可以不管分母了,用这样的方法解决了精度问题。那么怎么解决爆int范围的问题呢?我们发现,在每次操作的时候都对一个质数取模的话错解的几率不是非常大,那么我们就可以大胆地取模几个质数来判断,如果不放心可以多取模几个。

BearChild发现了一个神奇的质数:50033(如果使用这个质数的话是不需要用其余几个质数判断的)。

这样进行累加和是否为0的判断,可行的话将每个答案存下来,然后sort一遍输出即可。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#include<bits/stdc++.h>
using namespace std;

const int ONE=501;
const int MOD=50033;

int n;
int A[ONE];
int divisor[ONE][3],num[3];
int p[ONE];
int q[ONE];
int Repeat[ONE];
int cnt;

struct power
{
int l,r;
int value;
}a[ONE];

int cmp(const power &a,const power &b)
{
return (double)a.l/a.r < (double)b.l/b.r;
}

int get()
{
int res,Q=1; char c;
while( (c=getchar())<48 || c>57)
if(c=='-')Q=-1;
if(Q) res=c-48;
while((c=getchar())>=48 && c<=57)
res=res*10+c-48;
return res*Q;
}

int gcd(int a,int b)
{
int r=a%b;
while(r)
{
a=b;
b=r;
r=a%b;
}
return b;
}


void Deal(int x,int PD)
{
for(int i=1;i<=x;i++)
{
if(!(x%i)) divisor[++num[PD]][PD]=i;
}
}

int Check(int x,int y)
{
p[0]=q[0]=1;
for(int i=1;i<=n;i++)
{
p[i]=(long long)p[i-1]*y % MOD; q[i]=(long long)q[i-1]*x % MOD;
}

long long res=0;
for(int i=0;i<=n;i++)
{
res=(res+(long long)p[n-i]*q[i]*A[i]) % MOD;
}

if(!res) return 1;
return 0;
}

int main()
{
n=get();
for(int i=0;i<=n;i++) A[i]=get();
if(A[0]==0)
{
a[++cnt].l=0; a[cnt].r=1;
}
for(int i=0;i<=n;i++)
{
if(A[i])
{
Deal(abs(A[i]),1);
break;
}
}

for(int i=n;i>=0;i--)
{
if(A[i])
{
Deal(abs(A[i]),2);
break;
}
}

for(int i=1;i<=num[1];i++)
for(int j=1;j<=num[2];j++)
{
int x=divisor[i][1];
int y=divisor[j][2];
if(gcd(x,y)!=1) continue;
if(Check(x,y))
{
a[++cnt].l=x;
a[cnt].r=y;
}
if(Check(-x,y))
{
a[++cnt].l=-x;
a[cnt].r=y;
}
}

sort(a+1,a+cnt+1,cmp);
printf("%d\n",cnt);
for(int i=1;i<=cnt;i++)
{
if(a[i].r==1) printf("%d\n",a[i].l);
else printf("%d/%d\n",a[i].l,a[i].r);
}

}